using Unity.Burst;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Rendering;
using Unity.Transforms;
using UnityEngine;
using UnityEngine.Rendering;

public partial struct AStarSystem : ISystem {
	public NativeArray<AStarNode> nodes;
	public int targetIndex;
	public NativeArray<Unity.Mathematics.Random> randomArray;
	public float2 playerPosition;

	public void OnCreate(ref SystemState state) {
		randomArray = new NativeArray<Unity.Mathematics.Random>(Unity.Jobs.LowLevel.Unsafe.JobsUtility.MaxJobThreadCount, Allocator.TempJob);

		var seed = new System.Random();
		for (int i = 0; i < randomArray.Length; ++i) {
			randomArray[i] = new Unity.Mathematics.Random((uint)seed.Next());
		}

		World world = World.DefaultGameObjectInjectionWorld;
		var entityManager = world.EntityManager;

		GameObject prefab = Resources.Load<GameObject>("ObstacleCube");
		var renderMeshArray = new RenderMeshArray(new Material[] { prefab.GetComponent<MeshRenderer>().sharedMaterial },
			new Mesh[] { prefab.GetComponent<MeshFilter>().sharedMesh });

		var desc = new RenderMeshDescription(
		   shadowCastingMode: ShadowCastingMode.Off,
		   receiveShadows: false);

		var prototype = entityManager.CreateEntity();

		RenderMeshUtility.AddComponents(
		   prototype,
		   entityManager,
		   desc,
		   renderMeshArray,
		   MaterialMeshInfo.FromRenderMeshArrayIndices(0, 0)
		);

		entityManager.AddComponentData(prototype, new LocalTransform());

		nodes = new NativeArray<AStarNode>((49 * 2 + 1) * (49 * 2 + 1) + 10, Allocator.Persistent);
		// targetIndex = 49 * 149 + 49;

		// AStarInit();

		var ecb = new EntityCommandBuffer(Allocator.TempJob);
		var aStarInitJob = new AStarInitJob {
			randomArray = randomArray,
			Prototype = prototype,
			Ecb = ecb.AsParallelWriter(),
			Nodes = nodes,
		};
		var aStarInitHandle = aStarInitJob.Schedule((49 * 2 + 1) * (49 * 2 + 1), 64);
		aStarInitHandle.Complete();

		ecb.Playback(entityManager);
		ecb.Dispose();

		randomArray.Dispose();

		entityManager.DestroyEntity(prototype);

		var aStarMap = entityManager.CreateEntity();
		entityManager.AddComponentData(aStarMap, new AStarMap());
		entityManager.SetComponentData(aStarMap, new AStarMap { nodes = nodes });
	}

	[BurstCompile]
	public void OnUpdate(ref SystemState state) {
		foreach (var (_, b) in SystemAPI.Query<RefRO<PlayerTag>, RefRO<LocalTransform>>()) {
			playerPosition = b.ValueRO.Position.xz;
		}
		targetIndex = GetIndex((int)playerPosition.x, (int)playerPosition.y);

		// var aStarUpdateJob = new AStarUpdate() {
		// 	targetIndex = targetIndex,
		// 	Nodes = nodes,
		// };
		// aStarUpdateJob.ScheduleParallel(state.Dependency).Complete();
		// aStarUpdateJob.ScheduleParallel();

		state.Dependency = new AStarUpdate() {
			targetIndex = targetIndex,
			Nodes = nodes,
		}.ScheduleParallel(state.Dependency);

		state.Dependency.Complete();
	}

	// public void AStarInit()
	// {
	//     int index = 0;

	//     for (int j = 49; j >= -49; j--)
	//     {
	//         for (int i = -49; i <= 49; i++)
	//         {
	//             // var a = (i, j, index);
	//             // Debug.Log(a);
	//             AStarNode node = new AStarNode();

	//             node.index = index;
	//             index++;
	//             node.x = i;
	//             // node.y = j;

	//             int value = UnityEngine.Random.Range(0, 100);
	//             if (value <= 25)
	//             {
	//                 node.isWalkable = false;
	//             }
	//             else
	//             {
	//                 node.isWalkable = true;
	//             }

	//             nodes[index] = node;
	//             Debug.Log(nodes[index].x + " " + nodes[index].y + " " + nodes[index].isWalkable);
	//         }
	//     }
	// }

	private int GetIndex(int x, int y) {
		int width = 2 * 49 + 1;
		return (x + 49) + (49 - y) * width;
	}
}

[BurstCompile]
public partial struct AStarUpdate : IJobEntity {
	[ReadOnly] public NativeArray<AStarNode> Nodes;
	[ReadOnly] public int targetIndex;

	public void Execute(Entity entity, [EntityIndexInQuery] int entityIndexInQuery, ref AStarAgent aStarAgent, ref Agent agent, ref LocalTransform localTransform) {
		NativeList<AStarNode> closedList = new NativeList<AStarNode>(Allocator.Temp);
		NativeList<AStarNode> openList = new NativeList<AStarNode>(Allocator.Temp);

		float3 agentLocation = localTransform.Position;

		aStarAgent.myNodeIndex = GetIndex(Mathf.RoundToInt(agentLocation.x), Mathf.RoundToInt(agentLocation.z));

		if (aStarAgent.myNodeIndex == targetIndex) {
			return;
		}

		int startNodeIndex = aStarAgent.myNodeIndex;

		AStarNode node = Nodes[startNodeIndex];

		node.gCost = 0;
		node.hCost = math.abs(targetIndex - startNodeIndex);
		node.fCost = node.hCost;
		node.parentIndex = -1;

		// 初始化openList和closedList
		openList.Add(node);

		aStarAgent.count = -1;

		while (openList.Length > 0) {
			int myNodeIndex = FindLowestFCost(openList);
			AStarNode myNode = openList[myNodeIndex];

			openList.RemoveAt(myNodeIndex);

			closedList.Add(myNode);

			int newParentIndex = closedList.Length - 1;

			if (myNode.index == targetIndex) {
				int nextNodeIndex = newParentIndex;

				aStarAgent.count = 0;

				while (closedList[closedList[nextNodeIndex].parentIndex].parentIndex != -1) {
					nextNodeIndex = closedList[nextNodeIndex].parentIndex;
					aStarAgent.count++;
					if (aStarAgent.count > 100) {
						break;
					}
				}

				aStarAgent.nextNodeIndex = closedList[nextNodeIndex].index;
				(int x, int y) = GetCoordinate(aStarAgent.nextNodeIndex);
				agent.targetPos = new float2(x, y);
				break;
			}

			int newMovementCostToNeighbor = myNode.gCost + 1;

			foreach (int i in GetNeighbors(myNode.index, Nodes)) {
				if (IfContain(closedList, i) != -1 || !Nodes[i].isWalkable) {
					continue;
				}

				int neighborIndex = IfContain(openList, i);

				if (neighborIndex == -1) {
					AStarNode neighborNode = Nodes[i];
					neighborNode.gCost = newMovementCostToNeighbor;
					neighborNode.hCost = math.abs(targetIndex - i);
					neighborNode.fCost = neighborNode.gCost + neighborNode.hCost;
					neighborNode.parentIndex = newParentIndex;
					openList.Add(neighborNode);
				} else if (newMovementCostToNeighbor < openList[neighborIndex].gCost) {
					AStarNode neighborNode = openList[neighborIndex];
					neighborNode.gCost = newMovementCostToNeighbor;
					neighborNode.fCost = neighborNode.gCost + neighborNode.hCost;
					neighborNode.parentIndex = newParentIndex;
					openList[neighborIndex] = neighborNode;
				}
			}

			aStarAgent.count--;
		}

		// 清理
		closedList.Dispose();
		openList.Dispose();
	}

	/// <summary>
	/// 找到openList中fCost最小的节点，返回在openList中的序列
	/// </summary>
	/// <param name="openList"></param>
	/// <returns></returns>
	private int FindLowestFCost(NativeList<AStarNode> openList) {
		int lowestIndex = -1;
		int lowestFCost = int.MaxValue;
		int length = openList.Length;

		for (int i = 0; i < length; i++) {
			if (openList[i].fCost < lowestFCost) {
				lowestFCost = openList[i].fCost;
				lowestIndex = i;
			}
		}

		return lowestIndex;
	}


	/// <summary>
	/// 判断一个节点是否在列表中
	/// </summary>
	/// <param name="list">目标列表</param>
	/// <param name="index">当前序列</param>
	/// <returns>在目标列表的序列或-1</returns>
	private int IfContain(NativeList<AStarNode> list, int index) {
		int length = list.Length;
		for (int i = 0; i < length; i++) {
			if (list[i].index == index) {
				return i;
			}
		}

		return -1;
	}

	/// <summary>
	/// 获取邻居节点在map中的index
	/// </summary>
	/// <param name="current"></param>
	/// <param name="Nodes"></param>
	/// <returns></returns>
	private NativeList<int> GetNeighbors(int current, NativeArray<AStarNode> Nodes) {
		// 这里需要实现获取当前节点所有邻居的逻辑
		// 根据你的网格布局，可能需要检查上、下、左、右等方向的节点
		// 并确保它们是可行走的

		NativeList<int> neighbors = new NativeList<int>(Allocator.Temp);
		// 填充neighbors列表
		int x = Nodes[current].x;
		int y = Nodes[current].y;

		NativeArray<int> dx = new NativeArray<int>(4, Allocator.Temp);
		NativeArray<int> dy = new NativeArray<int>(4, Allocator.Temp);
		dx[0] = 1; dx[1] = -1; dx[2] = 0; dx[3] = 0;
		dy[0] = 0; dy[1] = 0; dy[2] = 1; dy[3] = -1;

		int width = 49;

		for (int i = 0; i < 4; i++) // 检查四个方向
		{
			int nx = x + dx[i]; // 新的x坐标
			int ny = y + dy[i]; // 新的y坐标

			// 检查新坐标是否在网格内，并且节点是可行走的
			if (nx >= -width && nx <= width && ny >= -width && ny <= width && Nodes[GetIndex(nx, ny)].isWalkable) {
				// 计算邻居节点的索引
				int neighborIndex = GetIndex(nx, ny);
				neighbors.Add(neighborIndex);
			}
		}

		return neighbors;
	}

	/// <summary>
	/// 获取节点的index
	/// </summary>
	/// <param name="x"></param>
	/// <param name="y"></param>
	/// <returns></returns>
	private int GetIndex(int x, int y) {
		int width = 2 * 49 + 1;
		return (x + 49) + (49 - y) * width;
	}

	/// <summary>
	/// 获取节点的坐标
	/// </summary>
	/// <param name="index"></param>
	/// <returns></returns>
	private (int x, int y) GetCoordinate(int index) {
		int width = 2 * 49 + 1;
		int x = index % width - 49;
		int y = 49 - index / width;
		return (x, y);
	}
}

[BurstCompile]
public struct AStarInitJob : IJobParallelFor {
	// 属性标签很重要
	[NativeDisableParallelForRestriction]
	public NativeArray<Unity.Mathematics.Random> randomArray;

	// JOB_ID
	[Unity.Collections.LowLevel.Unsafe.NativeSetThreadIndex]
	private int nativeThreadIndex;

	public NativeArray<AStarNode> Nodes;
	public Entity Prototype;
	public EntityCommandBuffer.ParallelWriter Ecb;

	public void Execute(int index) {
		Unity.Mathematics.Random rand = randomArray[nativeThreadIndex];
		int randomValue = rand.NextInt(0, 100);
		randomArray[nativeThreadIndex] = rand;

		AStarNode node = new AStarNode();

		node.index = index;
		(int x, int y) = GetCoordinate(node.index);
		node.x = x;
		node.y = y;

		if (randomValue <= 20) // 20%的概率设置为不可行走
							   // if (index == GetIndex(0, 0))
		{
			node.isWalkable = false;
			// node.isWalkable = true;
			var e = Ecb.Instantiate(index, Prototype);
			Ecb.SetComponent(index, e, new LocalTransform { Position = new float3(x, 0.5f, y), Scale = 1, Rotation = quaternion.identity });

			var obstacle = Ecb.CreateEntity(index);
			var temBuffer = Ecb.AddBuffer<ObstacleVertex>(index, obstacle);
			temBuffer.Add(new ObstacleVertex() { position = new float2(x - 0.5f, y - 0.5f) });
			temBuffer.Add(new ObstacleVertex() { position = new float2(x + 0.5f, y - 0.5f) });
			temBuffer.Add(new ObstacleVertex() { position = new float2(x + 0.5f, y + 0.5f) });
			temBuffer.Add(new ObstacleVertex() { position = new float2(x - 0.5f, y + 0.5f) });
		} else {
			node.isWalkable = true;
		}
		// node.isWalkable = true;

		Nodes[index] = node;
	}

	private (int x, int y) GetCoordinate(int index) {
		int width = 2 * 49 + 1;
		int x = index % width - 49;
		int y = 49 - index / width;
		return (x, y);
	}

	private int GetIndex(int x, int y) {
		int width = 2 * 49 + 1;
		return (x + 49) + (49 - y) * width;
	}
}




